feat(spec): item-level property names for the dashboard header-action repeater - #17227
Conversation
… repeater (#16458) DashboardHeaderAction fields carry a JSON Schema title; a new resolveMetadataFormSchemaTitles overlays metadataForms.<type>.fields.<path>.label onto a derived JSON Schema (stepping through an array's items so a repeater row property is <repeater>.<property>); GET /meta serves the localised schema beside the localised form; dashboardForm enumerates the header composite's children so the extractor emits their catalog keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…adataFormSchemaTitles (#16458) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…em-level-property-names
…and the header.actions row properties (#16458) Regenerated with pnpm i18n:extract after dashboardForm enumerated the header composite's children; the zh-CN / ja-JP / es-ES leaves are hand-translated, so the provenance companions carry no entry for them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…em-level-property-names
…em-level-property-names
…inst the card's inverted acceptance (#16458) Item ③'s first half prescribed the opposite of the truth: the card and its triage read `refreshInterval` as the spec's field and `refreshIntervalSeconds` as a catalog entry that "can never match". The direction is inverted — `refreshInterval` was renamed to `refreshIntervalSeconds` in @objectstack/spec 17 (#15680, ruling B on #14478) and is now a retiredKey tombstone that refuses at parse. The catalogs already named the live key; carrying the acceptance out literally would have written the tombstone into all four. The misreading came from a substring match — `refreshInterval` occurs in dashboard.zod.ts only inside `refreshIntervalSeconds`, in the rename comment and in the tombstone's own prose. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…em-level-property-names
The card reasoned that `.default(12)` on `DashboardSchema.columns` would be behaviour-preserving because the renderer already falls back to 12. Measured at objectui origin/main, it does not: a `columns`-less dashboard is inferred from the widget spans (4 unless a span exceeds 4, then 12), and the very next line switches positioned-grid vs responsive auto-flow on that value. The stop condition therefore fired on a stronger reading than the one it was written for, and the changeset now records what was measured rather than the premise. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
📓 Docs Drift CheckThis PR changes 3 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin aa95d5e8b44d0548785d53fe75ad7a8886f6640b && git checkout aa95d5e8b44d0548785d53fe75ad7a8886f6640b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bccf311100cad7affccd6cbfcddbd81fe734d97d 24402bf3600e9b2697489836c3986a6b48fee639 && git checkout -B drift-repro bccf311100cad7affccd6cbfcddbd81fe734d97d && git merge --no-ff 24402bf3600e9b2697489836c3986a6b48fee639
node scripts/docs-audit/affected-docs.mjs --json bccf311100cad7affccd6cbfcddbd81fe734d97d
|
Fixes #16458
Clause-②: yes
Upstream half of objectui#8218. Not on any release path.
What the maker saw
Studio's interface property panel renders
dashboard.header.actions[]as a table whosecolumn headers read
items.properties[k].title ?? kfrom the JSON Schema derived byz.toJSONSchema(DashboardSchema). None of the four item fields carried atitle, so thefallback arm ran for every locale, English included. As triage insisted, this is not a
translation gap — it is a missing authoring label in the contract, and it was fixed as one.
Mechanism A, as ruled: author
titleon the Zod item schema, and teach the resolver tooverlay bundle labels onto the derived JSON Schema. B is untouched and still available.
Acceptance, item by item
titleDashboardHeaderActionSchema's four fields author.meta({ title });dashboard.test.tspins the derived schemaresolveMetadataFormSchemaTitles; write-up incontent/docs/protocol/kernel/i18n-standard.mdxheader.*childrenheader.actions.PROProw keysrefreshInterval, notrefreshIntervalSecondscolumnsdeclares.default(12)⛔ Item ③'s first half prescribes the opposite of the truth
The card and its triage both read
refreshIntervalas the spec's live field andrefreshIntervalSecondsas a catalog entry that "can never match". The direction isinverted.
refreshIntervalwas renamed torefreshIntervalSecondsin@objectstack/spec17 (#15680, ruling B on #14478) and is now a
retiredKeytombstone — authoring it is aparse error, already pinned in
dashboard.test.ts. The live authorable key isrefreshIntervalSeconds, which the four catalogs anddashboard.form.tsalready named.Carrying that acceptance out literally would have written the tombstoned key into all four
catalogs and manufactured exactly the never-matching entry the card set out to remove.
The misreading is a substring artefact. Triage's control reading was "
refreshIntervaloccurs 6x in
dashboard.zod.ts" — but every one of those occurrences is insiderefreshIntervalSeconds, in the rename comment, or in the tombstone's own prose:So the catalogs needed no repair here. What they lacked was a guard, and this PR adds
one:
dashboard-header-children.test.tsnow pins that every catalog names the live key andnever the tombstone, and that the key the catalogs name is the key
dashboardFormdeclares.Ablation (one-time, restored; mutation proven on disk by byte hash before either leg):
renaming
refreshIntervalSecondstorefreshIntervalinen.metadata-forms.generated.ts— the card's literal acceptance — turns the pin red:
The dispatch required the round to stop and drop
.default(12)if any consumer's behaviourmoved. It moves. The card reasoned the renderer already falls back to 12, which would make
the default behaviour-preserving. Measured at objectui
origin/main,packages/plugin-dashboard/src/DashboardRenderer.tsx, it does not:A
columns-less dashboard is inferred from the widget spans — 4, not 12, whenever nowidget spans past 4 — and the next line switches the entire layout on that value
(positioned grid vs responsive auto-flow). Declaring the default would both retire the
inference and flip every auto-flow dashboard into the positioned grid. A default that
silently materialises a key is expensive to take back, so item ④ stayed out and the
changeset records the measurement rather than the premise.
gap/refreshIntervalSecondswere audited alongside and likewise left alone.
Changeset — graded here, not inherited
@objectstack/specminor,@objectstack/restpatch,@objectstack/platform-objectspatch.All three publish, measured rather than assumed:
specshipssrc/**/*.zod.tsinfiles[], so thedashboard.zod.tsedit publishes on itsown, and
distcarries the newresolveMetadataFormSchemaTitlesexport.restshipsdist;rest-server.tschanged.platform-objectsshipsdist— and the catalogs really do reach it. Grepped after build:header.actions.actionTypeis present indist/plugin.mjs,dist/plugin.jsanddist/metadata-translations/index.js, with a pre-existing key as the lit control.Verification
Full package suites, never a subset:
Gate families derived by
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-fed path list), exit codes landed to disk before reading, reconciled with
--ran:pnpm --filter @objectstack/spec check:generated— "All 15 generated artifacts are up todate"; the tree is clean after regeneration, so nothing was hand-edited.
pnpm check:type-check-debtreturns
PREREQUISITE NOT MET(exit 3, an OOM — not a red) under the standardNODE_OPTIONS=--max-old-space-size=4096, because the gate hands its own child tsc aCI-shaped 6144MB ceiling that the caller's clamp starves. At 8192MB on this box it is green:
check-type-check-coverage --re-measure: OK — 5 ledger entries re-measured, 55 raw tsc errors, none above its recorded number.Verified on final head
24402bf36. Heavy runs went throughscripts/pm/os-verify-lock.sh;two
queue-timeoutverdicts along the way were recorded as NOT MEASURED and re-run, neveras passes.
验收备注
header.actionsis fixed here. The general case is reported as an out-of-scope finding.header.showTitle/header.showDescription/header.actions. Those three now come from the platform catalog, so that overlay isredundant and can shrink — ⛔ not touched here (cross-repo); to be noted on objectui#8218.
needs:contract-reviewis on both carriers and stays on. This PR is parked outside thequeue deliberately: the review is blocked on tier budget, which is expected and safe.
⛔ Do not arm auto-merge, do not flip ready, do not clear the label.
维护者速读(草稿)
改了什么 — Studio 属性面板里「页眉 → 操作按钮」表格的四个列头,过去在所有语言下都显示
机器键(
label/actionUrl/ ...),包括英文。现在它们在契约里有了名字,并且任何语言的词条都能覆盖它。顺带把页眉三个子项的翻译从 objectui 的私有补丁挪回平台目录。
为什么改 — 这不是翻译缺失。取值路径是
items.properties[k].title ?? k,而没有任何item schema 带
title,所以回退分支永远执行。分诊席点名拒绝按 i18n 修:那样英文面照样漏,且每加一个语言再漏一次。
风险与代价(含回滚) — 只增不减:没有删除任何键、没有改变任何接受集、没有移动任何解析⚠️ 卡片要求的第 ④ 项(给
输出。回滚即 revert 本 PR,无数据迁移、无存量兼容问题。
columns声明默认值 12)故意没做 —— 实测渲染端在没写
columns时推断出的是 4 而不是 12,并且据此在「定位网格」与「自适应流式网格」之间切换;声明默认值会让所有流式看板改变布局。
这类"悄悄把键实体化"的默认值一旦发布就很难收回,所以按派发令的停止条件停下了。
席位意见 — (待补)
你要做的 — 这张卡挂着
needs:contract-review,PR 停在队列外等契约复核,这是预期状态。⛔ 请不要合并、不要摘标签。复核时最值得看的一点:卡片第 ③ 项的验收口径是反的(它要求
把目录改成一个已经作废、写了就报错的键),本 PR 拒绝执行并加了一条门禁把这个方向钉死。
🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code